﻿#region References
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using JigLibX.Physics;
using JigLibX.Geometry;
using JigLibX.Collision;
using Xen;
using Xen.Camera;
using Xen.Graphics;
using Xen.Ex.Graphics;
using Xen.Ex.Graphics2D;
using Xen.Input.State;
using Xen.Input;
#endregion

namespace Ness.Engine.Cameras
{
    /// <summary>
    /// Base class for a camera that is controlled by <see cref="PlayerInput"/>
    /// </summary>
    abstract class ControlledCamera3D : Camera3D, IUpdate, IDisposable
    {
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="updaterManager"></param>
        /// <param name="projection"></param>
        public ControlledCamera3D(Engine game, UpdateManager updaterManager, Projection projection)
            : base(projection, Matrix.Identity)
        {
            this.game = game;
            widthOver2 = game.Window.ClientBounds.Width / 2;
            heightOver2 = game.Window.ClientBounds.Height / 2;
            if (updaterManager != null)
                updaterManager.Add(this);
        }

        public Vector2 viewRotation;
        public Matrix rotation;
        public int widthOver2;
        public int heightOver2;
        public Vector2 angles = Vector2.Zero;
        private Vector2 movSens = Vector2.One;
        private Vector2 rotSens = Vector2.One;
        private bool disposed;
        private PlayerIndex playerIndex;
        public Engine game;
        public Vector3 pos;

        public Matrix multiMatrix
        {
            get
            {
                return CameraMatrix;
            }
            set
            {
                if (value != CameraMatrix)
                {
                    if (this.game.IS_CLIENT && pos != Position)
                    {
                        //If client, send data to server
                        game.client.SendCameraPosition(pos);
                    }
                    else if (pos != Position)
                    {
                        //If server, update data for server
                        game.server.SendCameraPosition(pos, game.playerID);
                    }
                    game.ResetCmdData();
                }
            }
        }

        /// <summary>
        /// Gets/Sets the player input index
        /// </summary>
        public PlayerIndex PlayerIndex
        {
            get { if (disposed) throw new ObjectDisposedException("this"); return playerIndex; }
            set { if (disposed) throw new ObjectDisposedException("this"); playerIndex = value; }
        }

        public Vector3 Target
        {
            get
            {
                Matrix cameraRotation = Matrix.CreateRotationX(angles.X) * Matrix.CreateRotationY(angles.Y);
                return Position + Vector3.Transform(Vector3.Forward, cameraRotation);
            }
            set
            {
                Vector3 forward = Vector3.Normalize(Position - value);
                Vector3 right = Vector3.Normalize(Vector3.Cross(forward, Vector3.Up));
                Vector3 up = Vector3.Normalize(Vector3.Cross(right, forward));

                Matrix test = Matrix.Identity;
                test.Forward = forward;
                test.Right = right;
                test.Up = up;
                angles.X = -(float)Math.Asin(test.M32);
                angles.Y = -(float)Math.Asin(test.M13);
            }
        }

        /// <summary>
        /// Gets/Sets the rotational sensitivity
        /// </summary>
        public Vector2 RotationSensitivity
        {
            get { if (disposed) throw new ObjectDisposedException("this"); return rotSens; }
            set { if (disposed) throw new ObjectDisposedException("this"); rotSens = value; }
        }
        /// <summary>
        /// Gets/Sets the movement sensitivity
        /// </summary>
        public Vector2 MovementSensitivity
        {
            get { if (disposed) throw new ObjectDisposedException("this"); return movSens; }
            set { if (disposed) throw new ObjectDisposedException("this"); movSens = value; }
        }

        /// <summary>
        /// Update method to implement
        /// </summary>
        /// <param name="state"></param>
        /// <param name="input"></param>
        protected abstract void Update(UpdateState state, InputState input);
        UpdateFrequency IUpdate.Update(UpdateState state)
        {
            if (disposed)
                return UpdateFrequency.Terminate;

            Update(state, state.PlayerInput[PlayerIndex].InputState);

            return UpdateFrequency.FullUpdate60hz;
        }

        /// <summary></summary>
        public bool IsDisposed
        {
            get { return disposed; }
        }

        /// <summary>
        /// Dispose this camera (the camera will stop listening to player input)
        /// </summary>
        public void Dispose()
        {
            disposed = true;
        }
    }
}
